package com.likuicat.picture_ddd.shared.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.likuicat.picture_ddd.shared.websocket.disruptor.PictureEditEventProducer;
import com.likuicat.picture_ddd.shared.websocket.model.PictureEditActionEnum;
import com.likuicat.picture_ddd.shared.websocket.model.PictureEditMessageTypeEnum;
import com.likuicat.picture_ddd.shared.websocket.model.PictureEditRequestMessage;
import com.likuicat.picture_ddd.shared.websocket.model.PictureEditResponseMessage;
import com.likuicat.picture_ddd.domain.user.entity.User;
import com.likuicat.picture_ddd.application.service.UserApplicationService;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


/**
 *  图片编辑 websocket 处理器
 */
@Component
public class PictureEditHandler extends TextWebSocketHandler {


    /**
     * 便于查看此时有哪些用户正在编辑图片
     */
    // 每张图片的编辑状态，key: pictureId, value: 当前正在编辑的用户 ID
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();

    /**
     * 为每个图片维护一个session集合
     */
    // 保存所有连接的会话，key: pictureId, value: 用户会话集合
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();

    @Resource
    private UserApplicationService userApplicationService;

    @Resource
    private PictureEditEventProducer pictureEditEventProducer;

    /**
     * 连接建立成功后，发送消息
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        // 取出拦截器中设置的 attributes
        User user = (User) session.getAttributes().get("user");
        long pictureId = (Long) session.getAttributes().get("pictureId");
        // 保存会话到集合中
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet() );
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        webSocketSessions.add(session);
        // 构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        pictureEditResponseMessage.setMessage(String.format("用户：%s 加入编辑",user.getUserName()));
        pictureEditResponseMessage.setUser(userApplicationService.getUserToUserVO(user));
        // 广播给同一张图片的用户
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    /**
     * 收到前端发送的消息，识别消息并处理
     * @param session
     * @param message 前端发送的消息
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        User user = (User) session.getAttributes().get("user");
        long pictureId = (Long) session.getAttributes().get("pictureId");

        // 将 json 转化为 PictureEditResponseMessage
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        // 生产消息
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);

    }

    /**
     * 处理进入图片编辑的消息
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     * @throws Exception
     */
    public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        //首先是用户进入编辑状态，要设置当前用户为编辑用户，并且向其他客户端发送消息：
        if(!pictureEditingUsers.containsKey(pictureId)){
            //设置为编辑用户
            pictureEditingUsers.put(pictureId,user.getId());
            //设置响应参数
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            pictureEditResponseMessage.setMessage(String.format("用户：%s 开始编辑图片",user.getUserName()));
            pictureEditResponseMessage.setUser(userApplicationService.getUserToUserVO(user));
            //向所有用户广播
            broadcastToPicture(pictureId,pictureEditResponseMessage);
        }
    }

    /**
     * 处理正在编辑图片的消息（广播除自己以外）
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     * @throws Exception
     */
    public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        //从 map 中取出编辑者 id 信息
        Long editUserId = pictureEditingUsers.get(pictureId);
        String editAction = pictureEditRequestMessage.getEditAction();
        PictureEditActionEnum editActionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        if(editActionEnum == null){
            //当前用户无操作
            return;
        }
        //确认操作者是本人
        if(editUserId != null && editUserId.equals(user.getId())){
            //构造响应参数
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            pictureEditResponseMessage.setUser(userApplicationService.getUserToUserVO(user));
            pictureEditResponseMessage.setMessage(String.format("%s 执行 %s 操作",user.getUserName(),editActionEnum.getText()));
            pictureEditResponseMessage.setEditAction(editAction);
            //向除自己以外的用户广播
            broadcastToPicture(pictureId,pictureEditResponseMessage,session);
        }


    }

    /**
     * 处理退出编辑信息
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     * @throws Exception
     */
    public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        //从 map 中取出编辑者 id 信息
        Long editUserId = pictureEditingUsers.get(pictureId);
        if(editUserId != null && editUserId.equals(user.getId())){
            //移除
            pictureEditingUsers.remove(pictureId);
            //构造响应参数
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            pictureEditResponseMessage.setUser(userApplicationService.getUserToUserVO(user));
            pictureEditResponseMessage.setMessage(String.format("用户：%s 退出编辑" ,user.getUserName()));
            //向所有用户广播
            broadcastToPicture(pictureId,pictureEditResponseMessage);
        }
    }

    /**
     * 关闭连接时
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        // 取出拦截器中设置的 attributes
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 删除会话，释放资源
        handleExitEditMessage(null, session, user, pictureId);
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        if(webSocketSessions != null){
            //移除该图片中当前用户的session
            webSocketSessions.remove(session);
            if(webSocketSessions.isEmpty()){
                //无用户编辑，直接移除该图片中的所有用户会话
                pictureSessions.remove(pictureId);
            }
        }
        //构造响应参数
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("%s 离开编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userApplicationService.getUserToUserVO(user));
        //广播
        broadcastToPicture(pictureId,pictureEditResponseMessage);
    }

    /**
     * 广播给除当前客户端的该图片的所有用户
     * @param pictureId
     * @param pictureEditResponseMessage
     * @param excludeSession 排除当前的webSocketSession客户端，给其他的发消息
     * @throws Exception
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws Exception {
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        if (CollUtil.isNotEmpty(webSocketSessions)) {
            //解决Long类型精度丢失问题（UserVO.getId）(将Long转为String)
            ObjectMapper objectMapper = new ObjectMapper();
            SimpleModule module = new SimpleModule();
            //添加序列化器
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(module);
            // 使用自定义序列化器，将消息转化json
            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);
            //遍历所有用户的WebSocketSession，发送消息
            for (WebSocketSession webSocketSession : webSocketSessions) {
                //排除当前session
                if(excludeSession != null && excludeSession.equals(webSocketSession)) {
                    continue;
                }
                //检查当前 WebSocket 连接是否仍然处于打开状态
                if(webSocketSession.isOpen()) {
                    ////向指定用户发送消息
                    webSocketSession.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * 广播给该图片的所有用户
     * @param pictureId
     * @param pictureEditResponseMessage
     * @throws Exception
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws Exception {
        broadcastToPicture(pictureId,pictureEditResponseMessage,null);
    }
}
